#pragma once
#include <iostream>
#include <sys/select.h>
#include <memory>
#include "Socket.hpp"
using namespace socket_ns;
using namespace std;
// select服务器可以以单进程同时处理多个用户的请求，这种策略就叫：多路转接。
// select服务器需要借助一个辅助数组，保存合法的fd，方便统一添加管理
class SelectServer
{
    // fd_set是位图类型，直接获取此位图的大小*8，就是该位图的比特位大小，该位图中存在多少个比特位，select就可以监管几个fd
    // select有缺点：select只能监管有限个fd，因为fd_set位图有上限，大小是固定的
    const static int N = sizeof(fd_set) * 8;
    const static int defaultfd = -1; // 缺省文件描述符

public:
    SelectServer(uint16_t port)
        : _port(port),
          _listensock(make_unique<TcpSocket>())
    {
        // 构建网络信息
        InetAddr addr("0.0.0.0", _port);
        // 获取listen套接字
        _listensock->BuildListenSocket(addr);
        // 初始化的时候，就需要将读的辅助数组进行初始化
        for (int i = 0; i < N; i++)
        {
            _fd_array[i] = defaultfd;
        }
        // 将初始化获取的listensockfd添加到_fd_array数组中
        _fd_array[0] = _listensock->SockFd();
    }
    // 处理listensockfd的工作-获取连接
    void AcceptClinet()
    {
        InetAddr clientaddr;                             // 客户端网络信息
        int sockfd = _listensock->Accepter(&clientaddr); // 在这里调用accept时，不会阻塞等待，因为select已经做了等待的操作了，这里直接会进行获取。
        // 返回文件描述符
        if (sockfd < 0)
        {
            return;
        }
        // 此时，获取的fd就是一个合法的fd
        LOG(DEBUG, "获取了一个新连接,sockfd:%d ,client ip:%s , client port:%d\n", sockfd, clientaddr.Ip().c_str(), clientaddr.Port());
        // 调用accept之后，会获取一个fd，应该将此新获取的fd交给select,让select去帮我们进行等待
        // 只需要将新获得的fd添加到_fd_array数组中，下次调用select时传递给它，让select帮我们去等待即可
        int pos = 1; // 从1位置开始去找缺省的fd位置
        for (; pos < N; pos++)
        {
            if (_fd_array[pos] == defaultfd)
            {
                // 找到了
                break;
            }
        }
        if (pos == N)
        {
            // 此时，在_fd_array数组中，fd已经写满整个数组了
            ::close(sockfd); // 只能将新获取的fd给关闭了,然后返回即可
            LOG(WARNING, "服务器满载了\n");
            return;
        }
        else
        {
            // 添加新的fd到_fd_array数组中
            _fd_array[pos] = sockfd;
            LOG(DEBUG, "fd:%d文件描述符已经被添加至读-辅助数组中了\n", sockfd);
        }
        LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
    }
    // 处理sockfd的工作-进行读写通信
    void ServiceIO(int pos)
    {
        char buffer[1024];                                           // 接收缓冲区
        ssize_t n = recv(_fd_array[pos], buffer, sizeof(buffer), 0); // 此时，调用recv读取不会进行阻塞等待，因为等待的工作select已经做过了，此时此recv接口直接获取数据即可
        if (n > 0)
        {
            // 读取成功
            buffer[n] = '\0';
            cout << "server收到了一个信息 # " << buffer << endl;
            string echo_string = "[Server Echo] # ";
            echo_string += buffer;
            // 发送
            ::send(_fd_array[pos], echo_string.c_str(), echo_string.size(), 0);
        }
        else if (n == 0)
        {
            // 客户端退出了
            ::close(_fd_array[pos]);
            LOG(DEBUG, "fd:%d 已经被关闭了\n", _fd_array[pos]);
            // 在_fd_array数组中将关闭了的fd进行释放
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
        }
        else
        {
            // 读取失败了
            ::close(_fd_array[pos]);
            // 在_fd_array数组中将关闭了的fd进行释放
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "fd:%d 读取失败\n", _fd_array[pos]);
            LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
        }
    }

    // 处理rfds-读事件中就绪了的fd
    void HanderEvent(fd_set &rfds)
    {
        // 此时，rfds中就会有很多已经就绪了的fd，我们要处理这些fd
        for (int i = 0; i < N; i++)
        {
            // 首先需要判断此fd是否合法，在_fd_array数组中
            if (_fd_array[i] == defaultfd)
            {
                // 这个fd是非法的
                continue;
            }
            // 此时，fd就是合法的，其次再判断此fd是否已经就绪了
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 此时fd就是合法且就绪了的
                // 但是，这个fd分为listensockfd和普通通信的socketfd，对于listensockfd来说，工作是accept获取连接，对于socketfd来说，工作是读写
                if (_fd_array[i] == _listensock->SockFd())
                {
                    // 处理listensockfd的工作-获取连接
                    AcceptClinet();
                }
                else
                {
                    // 这是普通socketfd，需要进行读写
                    ServiceIO(i); // 传递fd的下标，用此下标找到fd进行读写
                }
            }
        }
    }

    // 启动select服务
    void Loop()
    {
        while (true)
        {
            // 服务器首先要等待新连接的到来，这等价于客户端给服务器发送数据。将这两种都作为读事件统一进行处理
            // 新连接到来==发送数据==读事件就绪
            // 将新连接的listensock添加到读文件描述符集中。
            fd_set rfds;            // 读fd集
            FD_ZERO(&rfds);         // 清空rfds
            int max_fd = defaultfd; // 最大的合法的fd
            // 遍历所有可能出现的fd，将_fd_array数组中合法的fd添加到rfds中
            for (int i = 0; i < N; i++)
            {
                // 如果这个位置的fd是缺省的，就跳过下一个
                if (_fd_array[i] == defaultfd)
                {
                    continue;
                }
                // 走到这里此fd就不是缺省的，是合法的fd，就添加到_fd_array数组中
                FD_SET(_fd_array[i], &rfds); // 将fd添加到rfds中
                // 在合法的fd中找到最大的fd
                if (_fd_array[i] > max_fd)
                {
                    max_fd = _fd_array[i];
                }
            }
            // 此时，合法的fd也就添加进了rfds中了
            //  构建select等待策略
            struct timeval timeout{1, 0}; // 每个1秒0微秒等待
            int n = select(max_fd + 1, &rfds, nullptr, nullptr, &timeout /*nullptr:表示阻塞式等待*/);
            switch (n)
            {
            case -1:
            {
                // select出错
                LOG(ERROR, "select函数出错\n");
                break;
            }
            case 0:
            {
                // select函数超时了
                LOG(INFO, "select函数超时了,timeout:%d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            }
            default:
            {
                // 此时，select函数等待成功了，返回已经读就绪的文件描述符的个数
                LOG(DEBUG, "存在fd的读事件就绪了,select返回值:%d\n", n);
                // 底层只要由一个fd读事件就绪了，select就会一直通知服务器，让服务器去处理
                // 因为fd_set是一个输入输出型参数，返回时，已经就绪了的fd就在rfds位图中
                // 处理rfds-读事件中就绪了的fd
                HanderEvent(rfds);
                break;
            }
            }
        }
    }
    // 将所有的合法的fd输出到字符串中
    string RfdsToString()
    {
        string fdstr;
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultfd)
            {
                continue;
            }
            fdstr += to_string(_fd_array[i]);
            fdstr += " ";
        }
        return fdstr;
    }
    ~SelectServer()
    {
    }

private:
    uint16_t _port;                 // 端口号
    unique_ptr<Socket> _listensock; // 套接字指针
    int _fd_array[N];               // 辅助数组-关心读事件
};